/*
 * Copyright (c) 2011, Marc-Andre Ferland - madrang@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the The Warrent Team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * This software is free for non-commercial use. You may not use this
 *       software, in whole or in part, in support of any commercial product
 *       without the express consent of the author.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE WARRENT TEAM BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace OsHelper.UsbNet.LibUsb
{
	/// <summary>
    /// Represents a Libusb-1.0 device handle.
    /// </summary>
    /// <remarks>
    /// <para>To close a device, see the <see cref="Close"/> method.</para>
    /// <note title="Libusb-1.0 API Note:" type="cpp">A <see cref="MonoUsbDeviceHandle"/> is roughly equivalent to a <a href="http://libusb.sourceforge.net/api-1.0/group__dev.html#ga7df95821d20d27b5597f1d783749d6a4">libusb_device_handle</a>.</note>
    /// </remarks>
    /// <code>
    /// MonoUsbDeviceHandle deviceHandle = new MonoUsbDeviceHandle(profileHandle);
    /// if (deviceHandle.IsInvalid) throw new Exception("Invalid device context.");
    /// </code>
	public class Device : OsHelper.Handles.Handle
	{
		private static Dictionary<IntPtr, WeakReference> HandleList = new Dictionary<IntPtr, WeakReference>();
		
		public static bool GetDevice(IntPtr DeviceHandle, out LibUsb.Device ResultDevice)
		{
			WeakReference WeakRef = null;
			ResultDevice = null;
			
			if(!HandleList.TryGetValue(DeviceHandle, out WeakRef))
				return false;
			
			if(!WeakRef.IsAlive)
				return false;
			
			ResultDevice = (LibUsb.Device)WeakRef.Target;
			return true;
		}
		
		#region ConstructorDestructor
		
		/// <summary>Open a device handle from <paramref name="ProfileHandle"/>.</summary>
		/// <remarks>
		/// <para>A handle allows you to perform I/O on the device in question.</para>
		/// <para>This is a non-blocking function; no requests are sent over the bus.</para>
		/// </remarks>
		/// <param name="profileHandle">A device profile handle.</param>
		public Device(LibUsb.Profile MyProfile) : base()
		{
			IntPtr MyHandle = IntPtr.Zero;
			
			LibUsb.Error ErrorCode = Device.Open(MyProfile, ref MyHandle);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				this.SetHandle(MyHandle);
				HandleList.Add(this.handle, new WeakReference(this));
				break;
			case LibUsb.Error.NoMem:
				throw new OutOfMemoryException();
			case LibUsb.Error.AccessDenied:
				throw new UnauthorizedAccessException();
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			default:
				throw new Exception("Unknown error in Device:Open, LibUsb Code : " + ErrorCode);
			}
		}
		
		public Device (Session SessionHandle, short VendorID, short ProductID) : base()
		{
			IntPtr MyHandle = Device.OpenDeviceVidPid (SessionHandle, VendorID, ProductID);
			
			if (MyHandle == IntPtr.Zero || MyHandle == (IntPtr)(-1))
				throw new DeviceNotFoundException();
			
			this.SetHandle(MyHandle);
			HandleList.Add(this.handle, new WeakReference(this));
		}

        ///<summary>
        ///Closes the <see cref="DeviceHandle"/>.
        ///</summary>
        ///<returns>
        ///true if the <see cref="DeviceHandle"/> is released successfully; otherwise, in the event of a catastrophic failure, false. In this case, it generates a ReleaseHandleFailed Managed Debugging Assistant.
        ///</returns>
		protected override bool ReleaseHandle()
		{
			if (!this.IsInvalid)
			{
				Device.Close(this.handle);
				HandleList.Remove(this.handle);
				
				this.SetHandleAsInvalid();
			}
			return true;
		}
		
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_open_device_with_vid_pid")]
		private static extern IntPtr OpenDeviceVidPid ([In] Session SessionHandle, short vendorID, short productID);
		
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_open")]
		private static extern LibUsb.Error Open ([In] LibUsb.Profile ProfileHandle, ref IntPtr DeviceHandle);
		
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_close")]
		private static extern void Close (IntPtr DeviceHandle);
		
		#endregion
		
		/// <summary>
		/// Reset the device.
		/// </summary>
		/// <remarks>
		/// <para>The system will attempt to restore the previous configuration and
		/// alternate settings after the reset has completed.</para>
		/// <para>If the reset fails, the descriptors change,
		/// or the previous state cannot be restored,
		/// the device will appear to be disconnected and reconnected.
		/// This means that the device handle is no longer valid,
		/// you should close it and rediscover the device.
		/// The method will throw <see cref="Error.DeviceNotFoundException"/> when this is the case.</para>
		/// <para>This is a blocking function which usually incurs a noticeable delay.</para>
		/// </remarks>
		public void Reset()
		{
			LibUsb.Error ErrorCode = Device.ResetDevice(this);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			default:
				throw new Exception("Unknown error in Device:Reset, LibUsb Code : " + ErrorCode);
			}
		}
		
		/// <summary>
		/// Clear the halt/stall condition for an endpoint.
		/// </summary>
		public void ClearHalt(byte EndpointValue)
		{
			LibUsb.Error ErrorCode = Device.ClearHalt(this, EndpointValue);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			case Error.NotFound:
				throw new ArgumentOutOfRangeException("EndpointValue", "The specified endpoint does not exist");
			default:
				throw new Exception("Unknown error in Device:ClearHalt, LibUsb Code : " + ErrorCode);
			}
		}
		
		/// <summary>
        /// Perform a USB port reset to reinitialize a device. 
        /// </summary>
        /// <param name="deviceHandle">A device handle.</param>
        /// <returns>
        /// <list type="bullet">
        /// <item>0 on success</item>
        /// <item><see cref="LibUsb.Error.NotFound"/> if re-enumeration is required, or if the device has been disconnected</item>
        /// <item>another <see cref="LibUsb.Error"/> code on other failure</item>
        /// </list>
        /// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_reset_device")]
		private static extern LibUsb.Error ResetDevice ([In] Device DeviceHandle);
		
		/// <summary>
		/// Clear the halt/stall condition for an endpoint.
		/// </summary>
		/// <remarks>
		/// <para>Endpoints with halt status are unable to receive or transmit data until the halt condition is stalled.</para>
		/// <para>You should cancel all pending transfers before attempting to clear the halt condition.</para>
		/// <para>This is a blocking function.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="dev"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A device handle.</param>
		/// <param name="endpoint">The endpoint to clear halt status.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNotFound"/> if the endpoint does not exist</item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failure</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_clear_halt")]
		private static extern LibUsb.Error ClearHalt ([In] Device DeviceHandle, byte endpoint);
		
		#region Interface
		
		/// <summary>
		/// Claims the specified interface of the device.
		/// </summary>
		public void ClaimInterface (int InterfaceID)
		{
			LibUsb.Error ErrorCode = Device.ClaimInterface(this, InterfaceID);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			case LibUsb.Error.Busy:
				throw new ResourceBusyException();
			default:
				throw new Exception("Unknown error in Device:ClaimInterface, LibUsb Code : " + ErrorCode);
			}
		}

		/// <summary>
		/// Releases an interface that was previously claimed with <see cref="ClaimInterface"/>.
		/// </summary>
		public void ReleaseInterface (int InterfaceID)
		{
			LibUsb.Error ErrorCode = Device.ReleaseInterface(this, InterfaceID);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			default:
				throw new Exception("Unknown error in Device:ReleaseInterface, LibUsb Code : " + ErrorCode);
			}
		}
		
		/// <summary>
		/// Activate an alternate setting for an interface.
		/// </summary>
		public void SetInterfaceAltSetting (int InterfaceID, int AlternateSetting)
		{
			LibUsb.Error ErrorCode = Device.SetInterfaceAltSetting(this, InterfaceID, AlternateSetting);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			default:
				throw new Exception("Unknown error in Device:SetInterfaceAltSetting, LibUsb Code : " + ErrorCode);
			}
		}
		
		/// <summary>
		/// Claim an interface on a given device handle. 
		/// </summary>
		/// <remarks>
		/// <para>You must claim the interface you wish to use before you can perform I/O on any of its endpoints.</para>
		/// <para>It is legal to attempt to claim an already-claimed interface, in which case libusb just returns 0 without doing anything.</para>
		/// <para>Claiming of interfaces is a purely logical operation; it does not cause any requests to be sent over the bus. Interface claiming is used to instruct the underlying operating system that your application wishes to take ownership of the interface.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="dev"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A device handle.</param>
		/// <param name="interfaceNumber">the <see cref="AltInterfaceDescriptor.bInterfaceNumber"/> of the interface you wish to claim.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNotFound"/> if the requested interface does not exist</item>
		/// <item><see cref="Error.ErrorBusy"/> if another program or driver has claimed the interface </item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failure</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_claim_interface")]
		private static extern LibUsb.Error ClaimInterface ([In] Device DeviceHandle, int interfaceNumber);

		/// <summary>
		/// Release an interface previously claimed with <see cref="ClaimInterface"/>.
		/// </summary>
		/// <remarks>
		/// <para>You should release all claimed interfaces before closing a device handle.</para>
		/// <para>This is a blocking function. A SET_INTERFACE control request will be sent to the device, resetting interface state to the first alternate setting.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="dev"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A device handle.</param>
		/// <param name="interfaceNumber">the <see cref="AltInterfaceDescriptor.bInterfaceNumber"/> of the interface you wish to claim.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNotFound"/> if the interface was not claimed</item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failure</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_release_interface")]
		private static extern LibUsb.Error ReleaseInterface ([In] Device DeviceHandle, int interfaceNumber);
		
		/// <summary>
		/// Activate an alternate setting for an interface.
		/// </summary>
		/// <remarks>
		/// <para>The interface must have been previously claimed with <see cref="ClaimInterface"/>.</para>
		/// <para>You should always use this function rather than formulating your own SET_INTERFACE control request. This is because the underlying operating system needs to know when such changes happen.</para>
		/// <para>This is a blocking function.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="dev"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A device handle.</param>
		/// <param name="interfaceNumber">The <see cref="AltInterfaceDescriptor.bInterfaceNumber"/> of the previously-claimed interface.</param>
		/// <param name="alternateSetting">The <see cref="AltInterfaceDescriptor.bAlternateSetting"/> of the alternate setting to activate.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNotFound"/> if the interface was not claimed, or the requested alternate setting does not exist</item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failure</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_set_interface_alt_setting")]
		private static extern LibUsb.Error SetInterfaceAltSetting ([In] Device DeviceHandle, int interfaceNumber, int alternateSetting);
		
		#endregion
		
		#region Config
		
		public int Configuration
		{
			get {
				int ConfigVal = 0;
				LibUsb.Error ErrorCode = Device.GetConfiguration(this, ref ConfigVal);
				
				switch (ErrorCode) {
				case LibUsb.Error.Success:
					return ConfigVal;
				case LibUsb.Error.NoDevice:
					throw new DeviceNotFoundException();
				default:
					throw new Exception("Unknown error in Device:GetConfiguration, LibUsb Code : " + ErrorCode);
				}
			}
			set {
				LibUsb.Error ErrorCode = Device.SetConfiguration(this, value);
				
				switch (ErrorCode) {
				case LibUsb.Error.Success:
					return;
				case LibUsb.Error.NoDevice:
					throw new DeviceNotFoundException();
				default:
					throw new Exception("Unknown error in Device:SetConfiguration, LibUsb Code : " + ErrorCode);
				}
			}
		}
		
		/// <summary>
		/// Determine the <see cref="ConfigDescriptor.bConfigurationValue"/> of the currently active configuration. 
		/// </summary>
		/// <remarks>
		/// <para>You could formulate your own control request to obtain this information, but this function has the advantage that it may be able to retrieve the information from operating system caches (no I/O involved).</para>
		/// <para>If the OS does not cache this information, then this function will block while a control transfer is submitted to retrieve the information.</para>
		/// <para>This function will return a value of 0 in the <paramref name="configuration"/> parameter if the device is in unconfigured state.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="dev"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A device handle.</param>
		/// <param name="configuration">Output location for the <see cref="ConfigDescriptor.ConfigurationValue"/> of the active configuration. (only valid for return code 0)</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failure</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_get_configuration")]
		private static extern LibUsb.Error GetConfiguration ([In] Device DeviceHandle, ref int Configuration);

		/// <summary>
		/// Set the active configuration for a device. 
		/// </summary>
		/// <remarks>
		/// <para>The operating system may or may not have already set an active configuration on the device. It is up to your application to ensure the correct configuration is selected before you attempt to claim interfaces and perform other operations.</para>
		/// <para>If you call this function on a device already configured with the selected configuration, then this function will act as a lightweight device reset: it will issue a SET_CONFIGURATION request using the current configuration, causing most USB-related device state to be reset (altsetting reset to zero, endpoint halts cleared, toggles reset).</para>
		/// <para>You cannot change/reset configuration if your application has claimed interfaces - you should free them with <see cref="ReleaseInterface"/> first. You cannot change/reset configuration if other applications or drivers have claimed interfaces.</para>
		/// <para>A configuration value of -1 will put the device in unconfigured state. The USB specifications state that a configuration value of 0 does this, however buggy devices exist which actually have a configuration 0.</para>
		/// <para>You should always use this function rather than formulating your own SET_CONFIGURATION control request. This is because the underlying operating system needs to know when such changes happen.</para>
		/// <para>This is a blocking function.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="dev"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A device handle.</param>
		/// <param name="configuration">The <see cref="ConfigDescriptor.bConfigurationValue"/> of the configuration you wish to activate, or -1 if you wish to put the device in unconfigured state </param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNotFound"/> if the requested configuration does not exist</item>
		/// <item><see cref="Error.ErrorBusy"/> if interfaces are currently claimed</item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failure</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_set_configuration")]
		private static extern LibUsb.Error SetConfiguration ([In] Device DeviceHandle, int Configuration);
		
		#endregion
		
		#region KernelDriver
		
		public bool KernelDriverActive(int InterfaceID)
		{
			LibUsb.Error ErrorCode = Device.KernelDriverActive(this, InterfaceID);
			
			switch (ErrorCode) {
			case ((LibUsb.Error)1):
				return true;
			case LibUsb.Error.Success:
				return false;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			default:
				throw new Exception("Unknown error in Device:KernelDriverActive, LibUsb Code : " + ErrorCode);
			}
		}
		
		public void DetachKernelDriver(int InterfaceID)
		{
			LibUsb.Error ErrorCode = Device.DetachKernelDriver(this, InterfaceID);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			default:
				throw new Exception("Unknown error in Device:DetachKernelDriver, LibUsb Code : " + ErrorCode);
			}
		}
		
		public void AttachKernelDriver(int InterfaceID)
		{
			LibUsb.Error ErrorCode = Device.AttachKernelDriver(this, InterfaceID);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			default:
				throw new Exception("Unknown error in Device:AttachKernelDriver, LibUsb Code : " + ErrorCode);
			}
		}
		
		/// <summary>
		/// Determine if a kernel driver is active on an interface. 
		/// </summary>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 if no kernel driver is active.</item>
		/// <item>1 if a kernel driver is active.</item>
		/// <item><see cref="Error.NoDevice"/> if the device has been disconnected.</item>
		/// <item>Another <see cref="Error"/> code on other failure.</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_kernel_driver_active")]
		private static extern LibUsb.Error KernelDriverActive ([In] Device DeviceHandle, int InterfaceNumber);
		
		/// <summary>
		/// Detach a kernel driver from an interface.
		/// </summary>
		/// <remarks>
		/// <para>If successful, you will then be able to claim the interface and perform I/O.</para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="dev"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A device handle.</param>
		/// <param name="interfaceNumber">The interface to detach the driver from.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success.</item>
		/// <item><see cref="Error.ErrorNotFound"/> if no kernel driver was active.</item>
		/// <item><see cref="Error.ErrorInvalidParam"/> if the interface does not exist.</item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected </item>
		/// <item>Another <see cref="Error"/> code on other failure.</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_detach_kernel_driver")]
		private static extern LibUsb.Error DetachKernelDriver ([In] Device DeviceHandle, int interfaceNumber);

		/// <summary>
		/// Re-attach an interface's kernel driver, which was previously detached using <see cref="DetachKernelDriver"/>.
		/// </summary>
		/// <remarks>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="dev"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A device handle.</param>
		/// <param name="interfaceNumber">The interface to attach the driver from.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success.</item>
		/// <item><see cref="Error.ErrorNotFound"/> if no kernel driver was active.</item>
		/// <item><see cref="Error.ErrorInvalidParam"/> if the interface does not exist.</item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected.</item>
		/// <item><see cref="Error.ErrorBusy"/> if the driver cannot be attached because the interface is claimed by a program or driver.</item>
		/// <item>Another <see cref="Error"/> code on other failure.</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_attach_kernel_driver")]
		private static extern LibUsb.Error AttachKernelDriver ([In] Device DeviceHandle, int interfaceNumber);

		#endregion
		
		#region Synchronous device I/O
		
		/// <summary>
		/// Perform a USB control transfer.
		/// </summary>
		public int ControlTransfer (RequestAttributes MyAttributes, byte RequestType, short Value, short Index, byte[] Data, short Length, int Timeout)
		{
			LibUsb.Error ErrorCode = Device.ControlTransferInternal (this,
			                                                         (byte)MyAttributes,
			                                                         RequestType,
			                                                         Value,
			                                                         Index,
			                                                         Data,
			                                                         Length,
			                                                         Timeout);
			if((int)ErrorCode >= 0)
				return (int)ErrorCode;
			
			switch (ErrorCode) {
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			case LibUsb.Error.Pipe:
				throw new PipeErrorException(string.Format("Control endpoint halted. Request [RequestType: {0}, StandardRequests: {1}, Value: {2}, Index: {3}]",
				                                           (byte)MyAttributes, (byte)RequestType, Value, Index));
			default:
				throw new Exception(string.Format("Unknown error in Device:ControlTransfer, LibUsb Code: {0}, RequestType: {1}, StandardRequests: {2}, Value: {3}, Index: {4}",
				                                  ErrorCode, (byte)MyAttributes, (byte)RequestType, Value, Index));
			}
		}
		
		/// <summary>
		/// Perform a USB control transfer.
		/// </summary>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_control_transfer")]
		private static extern LibUsb.Error ControlTransferInternal ([In] Device DeviceHandle, byte RequestAttributes, byte RequestType, short Value, short Index, byte[] Data, short Length, int Timeout);

		/// <summary>
		/// Perform a USB bulk transfer. 
		/// </summary>
		public int BulkTransfer (byte Endpoint, byte[] Data, int Length, int Timeout)
		{
			int ActualLength = 0;
			LibUsb.Error ErrorCode = Device.BulkTransfer (this, Endpoint, Data, Length, out ActualLength, Timeout);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return ActualLength;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			case LibUsb.Error.Pipe:
				throw new PipeErrorException();
			default:
				throw new Exception("Unknown error in Device:BulkTransfer, LibUsb Code : " + ErrorCode);
			}
		}
		
		/// <summary>
		/// Perform a USB bulk transfer. 
		/// </summary>
		/// <remarks>
		/// <para>The direction of the transfer is inferred from the direction bits of the endpoint address.</para>
		/// <para>
		/// For bulk reads, the length field indicates the maximum length of data you are expecting to receive.
		/// If less data arrives than expected, this function will return that data, so be sure to check the 
		/// transferred output parameter.
		/// </para>
		/// <para>
		/// You should also check the transferred parameter for bulk writes. Not all of the data may have been 
		/// written. Also check transferred when dealing with a timeout error code. libusb may have to split 
		/// your transfer into a number of chunks to satisfy underlying O/S requirements, meaning that the 
		/// timeout may expire after the first few chunks have completed. libusb is careful not to lose any 
		/// data that may have been transferred; do not assume that timeout conditions indicate a complete lack 
		/// of I/O.
		/// </para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="syncio"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A handle for the device to communicate with.</param>
		/// <param name="endpoint">The address of a valid endpoint to communicate with.</param>
		/// <param name="pData">
		/// A suitably-sized data buffer for either input or output (depending on endpoint).</param>
		/// <param name="length">For bulk writes, the number of bytes from data to be sent. for bulk reads, the maximum number of bytes to receive into the data buffer.</param>
		/// <param name="actualLength">Output location for the number of bytes actually transferred.</param>
		/// <param name="timeout">Timeout (in milliseconds) that this function should wait before giving up due to no response being received. For an unlimited timeout, use value 0.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success (and populates <paramref name="actualLength"/>)</item>
		/// <item><see cref="Error.ErrorTimeout"/> if the transfer timed out</item>
		/// <item><see cref="Error.ErrorPipe"/> if the endpoint halted</item>
		/// <item><see cref="Error.ErrorOverflow"/>if the device offered more data, see <a href="http://libusb.sourceforge.net/api-1.0/packetoverflow.html">Packets and overflows</a></item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failures</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_bulk_transfer")]
		private static extern LibUsb.Error BulkTransfer ([In] Device MyDevice, byte Endpoint, byte[] Data, int Length, out int ActualLength, int Timeout);

		/// <summary>
		/// Perform a USB interrupt transfer. 
		/// </summary>
		/// <remarks>
		/// <para>
		/// The direction of the transfer is inferred from the direction bits of the endpoint address.
		/// </para><para>
		/// For interrupt reads, the length field indicates the maximum length of data you are expecting to receive.
		/// If less data arrives than expected, this function will return that data, so be sure to check the 
		/// transferred output parameter.
		/// </para><para>
		/// You should also check the transferred parameter for interrupt writes. Not all of the data may have been 
		/// written. Also check transferred when dealing with a timeout error code. libusb may have to split 
		/// your transfer into a number of chunks to satisfy underlying O/S requirements, meaning that the 
		/// timeout may expire after the first few chunks have completed. libusb is careful not to lose any 
		/// data that may have been transferred; do not assume that timeout conditions indicate a complete lack 
		/// of I/O.
		/// </para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="syncio"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A handle for the device to communicate with.</param>
		/// <param name="endpoint">The address of a valid endpoint to communicate with.</param>
		/// <param name="data">
		/// <para>A suitably-sized data buffer for either input or output (depending on endpoint).</para>
		/// This value can be:
		/// <list type="bullet">
		/// <item>An <see cref="Array"/> of bytes or other <a href="http://msdn.microsoft.com/en-us/library/75dwhxf7.aspx">blittable</a> types.</item>
		/// <item>An already allocated, pinned <see cref="GCHandle"/>. In this case <see cref="GCHandle.AddrOfPinnedObject"/> is used for the buffer address.</item>
		/// <item>An <see cref="IntPtr"/>.</item>
		/// </list>
		/// </param>
		/// <param name="length">For interrupt writes, the number of bytes from data to be sent. for interrupt reads, the maximum number of bytes to receive into the data buffer.</param>
		/// <param name="actualLength">Output location for the number of bytes actually transferred.</param>
		/// <param name="timeout">Timeout (in milliseconds) that this function should wait before giving up due to no response being received. For an unlimited timeout, use value 0.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success (and populates <paramref name="actualLength"/>)</item>
		/// <item><see cref="Error.ErrorTimeout"/> if the transfer timed out</item>
		/// <item><see cref="Error.ErrorPipe"/> if the endpoint halted</item>
		/// <item><see cref="Error.ErrorOverflow"/>if the device offered more data, see <a href="http://libusb.sourceforge.net/api-1.0/packetoverflow.html">Packets and overflows</a></item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failures</item>
		/// </list>
		/// </returns>
		public int InterruptTransfer (byte endpoint, byte[] Data, int Length, int Timeout)
		{
			int ActualLength = 0;
			LibUsb.Error ErrorCode = Device.InterruptTransfer (this, endpoint, Data, Length, out ActualLength, Timeout);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return ActualLength;
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException();
			case LibUsb.Error.Pipe:
				throw new PipeErrorException();
			default:
				throw new Exception("Unknown error in Device:InterruptTransfer, LibUsb Code : " + ErrorCode);
			}
		}
		
		/// <summary>
		/// Perform a USB interrupt transfer. 
		/// </summary>
		/// <remarks>
		/// <para>
		/// The direction of the transfer is inferred from the direction bits of the endpoint address.
		/// </para><para>
		/// For interrupt reads, the length field indicates the maximum length of data you are expecting to receive.
		/// If less data arrives than expected, this function will return that data, so be sure to check the 
		/// transferred output parameter.
		/// </para><para>
		/// You should also check the transferred parameter for interrupt writes. Not all of the data may have been 
		/// written. Also check transferred when dealing with a timeout error code. libusb may have to split 
		/// your transfer into a number of chunks to satisfy underlying O/S requirements, meaning that the 
		/// timeout may expire after the first few chunks have completed. libusb is careful not to lose any 
		/// data that may have been transferred; do not assume that timeout conditions indicate a complete lack 
		/// of I/O.
		/// </para>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="syncio"/></note>
		/// </remarks>
		/// <param name="deviceHandle">A handle for the device to communicate with.</param>
		/// <param name="endpoint">The address of a valid endpoint to communicate with.</param>
		/// <param name="pData">A suitably-sized data buffer for either input or output (depending on endpoint).</param>
		/// <param name="length">For interrupt writes, the number of bytes from data to be sent. for interrupt reads, the maximum number of bytes to receive into the data buffer.</param>
		/// <param name="actualLength">Output location for the number of bytes actually transferred.</param>
		/// <param name="timeout">Timeout (in milliseconds) that this function should wait before giving up due to no response being received. For an unlimited timeout, use value 0.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success (and populates <paramref name="actualLength"/>)</item>
		/// <item><see cref="Error.ErrorTimeout"/> if the transfer timed out</item>
		/// <item><see cref="Error.ErrorPipe"/> if the endpoint halted</item>
		/// <item><see cref="Error.ErrorOverflow"/>if the device offered more data, see <a href="http://libusb.sourceforge.net/api-1.0/packetoverflow.html">Packets and overflows</a></item>
		/// <item><see cref="Error.ErrorNoDevice"/> if the device has been disconnected</item>
		/// <item>another <see cref="Error"/> code on other failures</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_interrupt_transfer")]
		private static extern LibUsb.Error InterruptTransfer ([In] Device MyDevice, byte Endpoint, byte[] Data, int Length, out int ActualLength, int Timeout);
		
		#endregion
    }
}
